/**
 * License would add here
 */

#ifndef UTILS_JS_BIGINT_H
#define UTILS_JS_BIGINT_H

#include <csignal>
#include <cstring>
#include <js_native_api.h>

#include <node_api.h>

#include "js_number.h"
#include "js_value_base.h"
#include "macros.h"

class JSValueRef;

#define IS_BIGINT(type) VALUE_TYPE_CHECK(IsBigInt(), "bigint", (type))

typedef int64_t int64;
typedef uint64_t uint64;

#define BASIC_BIGINT_DATA(V) \
    V(int64, int64, value);  \
    V(uint64, uint64, value)

#define NAPI_BIGINT_DATA(V)                      \
    V(JSBigInt, int64, value.GetValue<int64>()); \
    V(napi_value, int64, JSBigInt(env_, value).GetValue<int64>())

#define ALL_BIGINT_DATA(V) \
    BASIC_BIGINT_DATA(V);  \
    NAPI_BIGINT_DATA(V)

class JSBigInt : public JSValue {
public:
    JSBigInt(napi_env env)
        : JSValue(env)
    {
        napi_create_bigint_int64(env, 0, &value_);
    }

#define VALUE_CONSTRUCTOR(type, out, V)                 \
    JSBigInt(napi_env env, type value)                  \
        : JSValue(env)                                  \
    {                                                   \
        napi_create_bigint_##type(env, value, &value_); \
        UpdateType();                                   \
    }

    BASIC_BIGINT_DATA(VALUE_CONSTRUCTOR);

#undef VALUE_CONSTRUCTOR

    JSBigInt(napi_env env, int sign, size_t count, uint64_t* words)
        : JSValue(env)
    {
        napi_create_bigint_words(env, sign, count, words, &value_);
        UpdateType();
    }
    JSBigInt(napi_env env, napi_value value)
        : JSValue(env, value)
    {
        IS_BIGINT(type_);
    }
    JSBigInt(const JSValue& value)
        : JSValue(value)
    {
        IS_BIGINT(type_);
    }

    template <typename T>
    inline T GetValue(bool* lossless = nullptr) const;

    template <typename T>
    inline operator T() const { return GetValue<T>(); };

#define DEFINE_VALUE_METHOD(type, out, V)                \
    inline JSBigInt operator=(const type& value)         \
    {                                                    \
        napi_create_bigint_##type(env_, value, &value_); \
        return *this;                                    \
    }

    BASIC_BIGINT_DATA(DEFINE_VALUE_METHOD)

#undef DEFINE_VALUE_METHOD

#define DEFINE_CALC_METHOD(type, out, V)           \
    inline JSBigInt operator+(const type& value);  \
    inline JSBigInt operator+=(const type& value); \
    inline JSBigInt operator-(const type& value);  \
    inline JSBigInt operator-=(const type& value)

    ALL_BIGINT_DATA(DEFINE_CALC_METHOD);

#undef DEFINE_CALC_METHOD

#define DEFINE_BINARY_METHOD(type, out, V)          \
    inline JSBigInt operator<<(const type& value);  \
    inline JSBigInt operator>>(const type& value);  \
    inline JSBigInt operator<<=(const type& value); \
    inline JSBigInt operator>>=(const type& value)

    ALL_BIGINT_DATA(DEFINE_BINARY_METHOD);

#undef DEFINE_BINARY_METHOD

#define DEFINE_LOGIC_METHOD(type, out, V)      \
    inline bool operator>(const type& value);  \
    inline bool operator>=(const type& value); \
    inline bool operator<(const type& value);  \
    inline bool operator<=(const type& value)

    ALL_BIGINT_DATA(DEFINE_LOGIC_METHOD);

#undef DEFINE_LOGIC_METHOD

#define DEFINE_STRICT_EQUAL(type, out, V) \
    inline bool operator==(const type& value)

    BASIC_BIGINT_DATA(DEFINE_STRICT_EQUAL);

#undef DEFINE_STRICT_EQUAL

private:
};

#define EXPAND_VALUE_METHOD(type, out, V)                              \
    template <>                                                        \
    inline type JSBigInt::GetValue<type>(bool* lossless) const         \
    {                                                                  \
        type result = 0;                                               \
        napi_get_value_bigint_##type(env_, value_, &result, lossless); \
        return result;                                                 \
    }

BASIC_BIGINT_DATA(EXPAND_VALUE_METHOD);

#undef EXPAND_VALUE_METHOD

#define EXPAND_CALC_METHOD(type, out, V)                              \
    inline JSBigInt JSBigInt::operator+(const type& value)            \
    {                                                                 \
        return JSBigInt(env_, GetValue<out>() + V);                   \
    }                                                                 \
    inline JSBigInt JSBigInt::operator+=(const type& value)           \
    {                                                                 \
        napi_create_bigint_##out(env_, GetValue<out>() + V, &value_); \
        return *this;                                                 \
    }                                                                 \
    inline JSBigInt JSBigInt::operator-(const type& value)            \
    {                                                                 \
        return JSBigInt(env_, GetValue<out>() - V);                   \
    }                                                                 \
    inline JSBigInt JSBigInt::operator-=(const type& value)           \
    {                                                                 \
        napi_create_bigint_##out(env_, GetValue<out>() - V, &value_); \
        return *this;                                                 \
    }

#define VB value
EXPAND_CALC_METHOD(int64, int64, VB)
EXPAND_CALC_METHOD(uint64, uint64, VB)
#undef VB
#define VJ value.GetValue<int64>()
EXPAND_CALC_METHOD(JSBigInt, int64, VJ)
#undef VJ
#define VN JSNumber(env_, value).GetValue<int64>()
EXPAND_CALC_METHOD(napi_value, int64, VN)
#undef VN

#undef EXPAND_CALC_METHOD

#define EXPAND_BINARY_METHOD(type, out, V)                             \
    inline JSBigInt JSBigInt::operator<<(const type& value)            \
    {                                                                  \
        return JSBigInt(env_, GetValue<out>() << V);                   \
    }                                                                  \
    inline JSBigInt JSBigInt::operator>>(const type& value)            \
    {                                                                  \
        return JSBigInt(env_, GetValue<out>() >> V);                   \
    }                                                                  \
    inline JSBigInt JSBigInt::operator<<=(const type& value)           \
    {                                                                  \
        napi_create_bigint_##out(env_, GetValue<out>() << V, &value_); \
        return *this;                                                  \
    }                                                                  \
    inline JSBigInt JSBigInt::operator>>=(const type& value)           \
    {                                                                  \
        napi_create_bigint_##out(env_, GetValue<out>() >> V, &value_); \
        return *this;                                                  \
    }

ALL_BIGINT_DATA(EXPAND_BINARY_METHOD)

#undef EXPAND_BINARY_METHOD

#define EXPAND_LOGIC_METHOD(type, out, V)               \
    inline bool JSBigInt::operator<(const type& value)  \
    {                                                   \
        return GetValue<out>() < V;                     \
    }                                                   \
    inline bool JSBigInt::operator>(const type& value)  \
    {                                                   \
        return GetValue<out>() > V;                     \
    }                                                   \
    inline bool JSBigInt::operator<=(const type& value) \
    {                                                   \
        return GetValue<out>() <= V;                    \
    }                                                   \
    inline bool JSBigInt::operator>=(const type& value) \
    {                                                   \
        return GetValue<out>() >= V;                    \
    }

ALL_BIGINT_DATA(EXPAND_LOGIC_METHOD);

#undef EXPAND_LOGIC_METHOD

#define EXPAND_STRICT_EQUAL(type, out, V)               \
    inline bool JSBigInt::operator==(const type& value) \
    {                                                   \
        return GetValue<out>() == V;                    \
    }

BASIC_BIGINT_DATA(EXPAND_STRICT_EQUAL);

#undef EXPAND_STRICT_EQUAL

#undef ALL_BIGINT_DATA
#undef BASIC_BIGINT_DATA
#undef NAPI_BIGINT_DATA

#undef IS_BIGINT
#endif /* UTILS_JS_BIGINT_H */
